package agents.task;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import agents.Action;
import agents.Agent;
import agents.Ant;
import agents.Environment;
import agents.exceptions.ActionFailedException;
import agents.resource.CrossRoadAgent;
import agents.resource.ResourceAgent;
import agents.resource.RoadAgent;
import domain.Car;
import domain.Clock;
import domain.Task;

public class TaskAgent implements Agent {

	private static final int MAX_NB_OF_TRIES = 3;//aantal keer dat de agent probeert om tot dezelfde resource te graken na elkaar
	private static final long CHANGE_TRIP_TRESHOLD = 2; //drempel om te veranderen van trip
	private static boolean USE_DEADLOCK = false;

	public TaskAgent(Task task) {
		this.task = task;
	}
	
	public List<Ant> getAnts() {
		if(nextAntsToSent == 0) {
			nextAntsToSent++;
			return getExplorationAnts();
		}
		else if(nextAntsToSent == 1){
			nextAntsToSent++;
			return getIntentionAnts();
		}
		return new ArrayList<Ant>();
	}
	
	private List<Ant> getExplorationAnts() {
		ArrayList<Ant> newAnts = new ArrayList<Ant>();
		newAnts.add(ExplorationAnt.createExplorationAnt(this, getTask().getDestination()));
		return newAnts;
	}
	
	private List<Ant> getIntentionAnts() {
		List<Ant> lst = new ArrayList<Ant>();
		
		if(exploredTrips.size() != 0) { //geen intention ants maken als geen gedacht over mogelijke trips (geen explor ant kwam terug
			Trip bestTrip = null;
			long bestTime = Long.MAX_VALUE;
			
			for(Trip trip : exploredTrips) {
				long tripTime = trip.getEstimatedTotalTime();
				if(tripTime < bestTime) {
					bestTime = tripTime;
					bestTrip = trip;
				}
			}
			
			Trip newPlannedTripEstimation = getNewPlannedTripExploration();
			
			if(newPlannedTripEstimation != null && newPlannedTripEstimation.getEstimatedTotalTime() - bestTrip.getEstimatedTotalTime() > CHANGE_TRIP_TRESHOLD) {
				bestTrip = newPlannedTripEstimation;
			}
	
			lst.add(new IntentionAnt(bestTrip,this));
			
			exploredTrips = new ArrayList<Trip>(); //verwijder verwerkte trips
		} else {
			plannedTrip = null;
		}
		
		return lst;		
	}

	private Trip getNewPlannedTripExploration() {
		if(plannedTrip == null) return null; //niks gepland ==> return null
		
		Trip updatedPlannedTrip = new Trip(plannedTrip);
		
		if(updatedPlannedTrip.getAllSteps().size() > 1 && updatedPlannedTrip.getAllSteps().get(1).getResourceAgent() == getCurrentResource()) {
			updatedPlannedTrip.removeStep(0);
		}		
		
		Trip newPlannedTripExploration = null;
		for(Trip trip : exploredTrips) {	//dezelfde route zoeken als updatedPlannedTrip met de vroegste aankomsttijd 
			if(trip.hasSameRouteAs(updatedPlannedTrip)) {
				if(newPlannedTripExploration == null || trip.getEstimatedTotalTime() < newPlannedTripExploration.getEstimatedTotalTime())
					newPlannedTripExploration = trip;
			}
		}
		
		return newPlannedTripExploration;
	}

	public Car getCar() {
		return getTask().getCar();
	}
	
	/* positie (=ResourceAgent) waar auto zich bevindt */
	// wordt opgevraagd door te zoeken in de omgeving
	//is null als task agent zijn taak voltooid heeft (en auto dan dus van de baan is)
	public ResourceAgent getCurrentResource() {
		return Environment.getInstance().getResourceAgentAt(getCar().getPosition());
	}
	
	//checkt ofdat de taak voltooid is
	public boolean isCompleted() {
		return getCar().getPosition() == null;
	}
	
	//doet taskagent met zijn auto verdwijnen
	private void disappear() {
		getCar().disappear();
		Environment.getInstance().removeAgent(this);
	}
	
	//interageert met resource agents om actie uit te voeren
	public Action getAction() {
		
		if(isCompleted()) return null;
		
		if(isAtDestination()) {
			return new Action() {
				public void performAction() throws ActionFailedException {
					disappear();
				}
			};
		}
		
		//op weg en niet op einde ==> altijd vooruit proberen te gaan
		final ResourceAgent currentPosition = getCurrentResource();
		if(currentPosition instanceof RoadAgent) {
			final RoadAgent ra = (RoadAgent) currentPosition;
			boolean atEndOfRoad = ra.isAtEndOfResource(getCar());
			if(!atEndOfRoad) { 
				return new Action() {
					public void performAction() throws ActionFailedException {
						ra.advance(getCar());
					}
				};
			}
		}
		
		//op einde weg of op crossroad ==> alleen vooruit gaan als gepland
		
		//geen info gekregen van exploratie of op eindresource => probeer maar voort te gaan naar willekeurige uitweg
		if(plannedTrip == null || plannedTrip.getAllSteps().size() == 0){ 
			List<ResourceAgent> outs = currentPosition.getOutgoingResources();
			//int randPos = (int) (Math.random()*outs.size());
			if(outs.size() == 0) return null; //geen uitweg ==> pech, zit vast
			tryNb %= outs.size();
			final ResourceAgent nextResource = outs.get(tryNb);
			tryNb++;
			return new Action() {
				public void performAction() throws ActionFailedException {
					currentPosition.moveTo(getCar(),nextResource);
				}
			};					
		}
		
		final ResourceAgent nextResource = plannedTrip.getFirstStep().getResourceAgent();
		if(plannedTrip.getFirstStep().getEnterTime() <= Clock.getInstance().getCurrentTime()) {
			return new Action() {
				public void performAction() throws ActionFailedException {
					currentPosition.moveTo(getCar(),nextResource);
				}
			};
		} else if(nextResource.getIngoingResources().size() == 1) { //volgende weg kan alleen in genomen worden door deze agent ==> voortgaan
			return new Action() {
				public void performAction() throws ActionFailedException {
					currentPosition.moveTo(getCar(),nextResource);
				}
			};
		}

		return null; //=> wachten
	}
	
	private int tryNb = 0;


	private boolean isAtDestination() {
		return getCar().getPosition() == getTask().getDestination();
	}

	public void think() {
		nextAntsToSent = 0;
		if(isAtDestination() || isCompleted()) {
			nextAntsToSent = 2;
		}
		if(USE_DEADLOCK)
			checkDeadLock();
	}

	private void checkDeadLock() {
		//checken of hij op hetzelfde crossroad blijft staan
		if(lastAgent == getCurrentResource() && lastAgent instanceof CrossRoadAgent) {
			//indien hij niet weet welke weg, geen plannedtrip, niks doen
			if(plannedTrip != null) {
				//geeft dezelfde oplossing als verleden keer, zit vast
				if(plannedTrip.getFirstStep().getResourceAgent() ==  plannedNextAgent){
					countTimeStuck ++;
					//zit lang genoeg vast, kies volgende beste oplossing
					if(countTimeStuck >= MAX_NB_OF_TRIES)
						routesStuck.add(plannedNextAgent);
				}else{
					plannedNextAgent = plannedTrip.getFirstStep().getResourceAgent();
					countTimeStuck = 0;
				}
			}
		} else {
			routesStuck.clear();
		}
		lastAgent = getCurrentResource();
	}
	
	private ResourceAgent lastAgent;
	private Collection<ResourceAgent> routesStuck = new ArrayList<ResourceAgent>();
	private ResourceAgent plannedNextAgent;
	private int countTimeStuck = 0;//tel hoeveel keer de eerste stap dezelfde is ==> patstelling
	private int nextAntsToSent=2; //0 = exploration, 1=intention, 2=niks
	
	private final Task task;


	//notificatie van terugkomst van een intentie ant
	public void notifyIntentionAntArrival(IntentionAnt ant) {
		plannedTrip = ant.getBookedTrip();
	}
	private Trip plannedTrip;
	
	//notificatie van terugkomst van een exploration ant
	//task agent extraheert informatie uit ant en bewaart deze tot intention ant wordt uitgestuurd
	public void notifyExplorationAntArrival(ExplorationAnt ant) {
		Trip newTrip = ant.getTrip();
		if(!routesStuck.contains(newTrip.getFirstStep().getResourceAgent()))
			exploredTrips.add(ant.getTrip());
	}
	
	private Collection<Trip> exploredTrips = new ArrayList<Trip>();
	
	@Override
	public String toString() {
		return getCar().getId();
	}

	public Task getTask() {
		return task;
	}
}
